/*
    This file is part of Blue Violin.

    Blue Violin is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Blue Violin is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero Public License for more details.

    You should have received a copy of the GNU Affero Public License
    along with Blue Violin.  If not, see <http://www.gnu.org/licenses/>.

    Blue Violin  Copyright (C) 2008  Blue Violin, Inc. & Josh Watts <josh.watts@blueviolin.org>
	For more information, visit http://www.blueviolin.org/

    This program comes with ABSOLUTELY NO WARRANTY.

*/
#ifndef __IEBROWSER_H_
#define __IEBROWSER_H_


#include "RatchetBrowserCP.h"
#include "resource.h"       // main symbols
#include "windows.h"
#include "gdiplus.h"
#include <sys/timeb.h>
#include <atlctl.h>
#include <vector>
#include <map>
#include "Util.h"


using namespace std;

typedef vector<IUnknown *> VECTORIUNKNOWNPOINTER;
typedef map<IUnknown *, DWORD> MAPADVISE;


#define RIE_DOCCOMPLETE 0xA000 + 1
#define RIE_POPUPIDISPATCH 0xA000 + 2


#define TRUE 1
#define FALSE 0

#define VK_0 0x30
#define VK_9 0x39
#define VK_A 0x41
#define VK_Z 0x5A




/////////////////////////////////////////////////////////////////////////////
// CIEBrowser
class ATL_NO_VTABLE CIEBrowser : 
	public CComObjectRootEx<CComSingleThreadModel>,
	public IDispatchImpl<IIEBrowser, &IID_IIEBrowser, &LIBID_RATCHETBROWSERLib>,
	public CComControl<CIEBrowser>,
	public IPersistStreamInitImpl<CIEBrowser>,
	public IOleControlImpl<CIEBrowser>,
	public IOleObjectImpl<CIEBrowser>,
	public IOleInPlaceActiveObjectImpl<CIEBrowser>,
	public IViewObjectExImpl<CIEBrowser>,
	public IOleInPlaceObjectWindowlessImpl<CIEBrowser>,
	public ISupportErrorInfo,
	public IConnectionPointContainerImpl<CIEBrowser>,
	public IPersistStorageImpl<CIEBrowser>,
	public ISpecifyPropertyPagesImpl<CIEBrowser>,
	public IQuickActivateImpl<CIEBrowser>,
	public IDataObjectImpl<CIEBrowser>,
	public IProvideClassInfo2Impl<&CLSID_IEBrowser, &DIID_DIEBrowserEvents, &LIBID_RATCHETBROWSERLib>,
	public IPropertyNotifySinkCP<CIEBrowser>,
	public CComCoClass<CIEBrowser, &CLSID_IEBrowser>,
	public IDispEventSimpleImpl<1, CIEBrowser, &__uuidof(DWebBrowserEvents2)>,
	public CProxy_IIEBrowserEvents< CIEBrowser >,
	public IPersistPropertyBag
{
public:
	CIEBrowser();
	~CIEBrowser();


DECLARE_REGISTRY_RESOURCEID(IDR_IEBROWSER)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CIEBrowser)
	COM_INTERFACE_ENTRY(IIEBrowser)
	COM_INTERFACE_ENTRY(IDispatch)
	COM_INTERFACE_ENTRY(IViewObjectEx)
	COM_INTERFACE_ENTRY(IViewObject2)
	COM_INTERFACE_ENTRY(IViewObject)
	COM_INTERFACE_ENTRY(IOleInPlaceObjectWindowless)
	COM_INTERFACE_ENTRY(IOleInPlaceObject)
	COM_INTERFACE_ENTRY2(IOleWindow, IOleInPlaceObjectWindowless)
	COM_INTERFACE_ENTRY(IOleInPlaceActiveObject)
	COM_INTERFACE_ENTRY(IOleControl)
	COM_INTERFACE_ENTRY(IOleObject)
	COM_INTERFACE_ENTRY(IPersistStreamInit)
	COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit)
	COM_INTERFACE_ENTRY(ISupportErrorInfo)
	COM_INTERFACE_ENTRY(IConnectionPointContainer)
	COM_INTERFACE_ENTRY(ISpecifyPropertyPages)
	COM_INTERFACE_ENTRY(IQuickActivate)
	COM_INTERFACE_ENTRY(IPersistStorage)
	COM_INTERFACE_ENTRY(IDataObject)
	COM_INTERFACE_ENTRY(IProvideClassInfo)
	COM_INTERFACE_ENTRY(IProvideClassInfo2)
	COM_INTERFACE_ENTRY_IMPL(IConnectionPointContainer)
	COM_INTERFACE_ENTRY(IPersistPropertyBag)
END_COM_MAP()

BEGIN_PROP_MAP(CIEBrowser)
	PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4)
	PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)
END_PROP_MAP()

BEGIN_CONNECTION_POINT_MAP(CIEBrowser)
	CONNECTION_POINT_ENTRY(IID_IPropertyNotifySink)
	CONNECTION_POINT_ENTRY(DIID_DIEBrowserEvents)
END_CONNECTION_POINT_MAP()

BEGIN_MSG_MAP(CIEBrowser)
	CHAIN_MSG_MAP(CComControl<CIEBrowser>)
	DEFAULT_REFLECTION_HANDLER()
	MESSAGE_HANDLER(WM_CREATE, OnCreate)
	MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
END_MSG_MAP()

// for fix to compiler error - error C2668: 'InlineIsEqualGUID' : ambiguous call to overloaded function
// http://support.microsoft.com/kb/q243298/
// ISupportsErrorInfo
	STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid)
	{
		static const IID* arr[] = 
		{
			&IID_IIEBrowser,
		};
		for (int i=0; i<sizeof(arr)/sizeof(arr[0]); i++)
		{
			if (InlineIsEqualGUID(*arr[i], riid))
				return S_OK;
		}
		return S_FALSE;
	}

// IViewObjectEx
	DECLARE_VIEW_STATUS(VIEWSTATUS_SOLIDBKGND | VIEWSTATUS_OPAQUE)

// IIEBrowser
public:
	STDMETHOD(get_BrowserID)(/*[out, retval]*/ BSTR *pVal);
	STDMETHOD(put_BrowserID)(/*[in]*/ BSTR newVal);
	STDMETHOD(get_Document)(/*[out, retval]*/ IDispatch* *pVal);
	STDMETHOD(Navigate2)(VARIANT *varURL, VARIANT *varFlags, VARIANT *varTargetFrameName, VARIANT *varPostData, VARIANT *varHeaders);
	STDMETHOD(WaitForDocumentComplete)();
	STDMETHOD(ReloadDOM)();
	STDMETHOD(get_BrowserIDispatch)(IDispatch **pVal);
	STDMETHOD(put_PopupIDispatch)(IDispatch **pVal);
	STDMETHOD(get_MousedownIsMouseclick)(BOOL *pVal);
	STDMETHOD(put_MousedownIsMouseclick)(BOOL newVal);
	STDMETHOD(get_ignoreEvents)(BOOL *pbVal);
	STDMETHOD(put_ignoreEvents)(BOOL bNewVal);
	STDMETHOD(Sleep)(long nMilliseconds);
	STDMETHOD(GoBack)();
	STDMETHOD(GoForward)();
	STDMETHOD(GoHome)();
	STDMETHOD(Stop)();
	STDMETHOD(Refresh2)(VARIANT *varLevel);
	STDMETHOD(ScreenCapture)(VARIANT_BOOL vbFullScreen,
							 BSTR bstrUploadUrl,
							 BSTR *pbstrEncodedImage);
	STDMETHOD(SendKeys)(BSTR bstrValue);
	STDMETHOD(CheckDocumentComplete)(BOOL *pbVal);
	// IPersistPropertyBag methods
	STDMETHOD(InitNew)();
	STDMETHOD(Load)(IPropertyBag *ppropBag, IErrorLog *perrorLog);
	STDMETHOD(Save)(IPropertyBag *ppropBag, BOOL bClearDirty, BOOL bSaveAll);
	STDMETHOD(GetClassID)(CLSID *pClassID);
	
	

	HRESULT OnDrawAdvanced(ATL_DRAWINFO& di)
	{
		return S_OK;
	}


	// called when the ActiveX control is created - should probably be moved to IEBrowser.cpp
	LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		_bstr_t bstrProgID = "InternetExplorer.WebBrowser";

		CAxWindow wnd(m_hWnd);
		HRESULT hr;
		m_hwndParent = this->GetParent();
		ModifyStyle(0, WS_HSCROLL | WS_VSCROLL | WS_TABSTOP);
		// hr = wnd.CreateControl(bstrProgID, NULL, NULL);
		hr = wnd.CreateControl(IDR_HTML_DEFAULT, NULL, NULL);
		if (SUCCEEDED(hr))
			hr = wnd.QueryControl(IID_IWebBrowser2, (void**)&m_spIEBrowser);
		IDispEventSimpleImpl<1, CIEBrowser, &__uuidof(DWebBrowserEvents2)>::DispEventAdvise(m_spIEBrowser);
		bDestroy = 1;
		m_spIEBrowser->put_RegisterAsBrowser(VARIANT_TRUE);
		Fire_BrowserCreated();
		return 0;
	}

	LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		IUnknown *punkBrowser = NULL;
		HRESULT hr;
		if (1 == bDestroy)
		{
			hr = m_spIEBrowser->QueryInterface(IID_IUnknown, (void**)&punkBrowser);
			if (SUCCEEDED(hr))
			{
				hr = IDispEventSimpleImpl<1, CIEBrowser, &__uuidof(DWebBrowserEvents2)>::DispEventUnadvise(m_spIEBrowser);
			}
			bDestroy = 0;
			m_spIEBrowser = NULL;
		}
		return 0;
	}

	CComPtr<IWebBrowser2> m_spIEBrowser;
	CComPtr<IWebBrowser2> m_spIEPopupBrowser;
	IDispatch *m_pPopupIDispatch;
	CAxWindow m_wndMain;

// probably should use our ::Invoke method to handle these events instead of using a sink map
// this Sink Map calls our methods when the Web Browser control fires events
BEGIN_SINK_MAP(CIEBrowser)
	SINK_ENTRY_INFO(1, __uuidof(DWebBrowserEvents2), DISPID_COMMANDSTATECHANGE, WebBrowserEvents2_CommandStateChange, NULL)
	SINK_ENTRY_INFO(1, __uuidof(DWebBrowserEvents2), DISPID_DOCUMENTCOMPLETE, WebBrowserEvents2_DocumentComplete, NULL)
	SINK_ENTRY_INFO(1, __uuidof(DWebBrowserEvents2), DISPID_BEFORENAVIGATE2, WebBrowserEvents2_BeforeNavigate2, NULL)
	SINK_ENTRY_INFO(1, __uuidof(DWebBrowserEvents2), DISPID_PROGRESSCHANGE, WebBrowserEvents2_ProgressChange, NULL)
	SINK_ENTRY_INFO(1, __uuidof(DWebBrowserEvents2), DISPID_STATUSTEXTCHANGE, WebBrowserEvents2_StatusTextChange, NULL)
	SINK_ENTRY_INFO(1, __uuidof(DWebBrowserEvents2), DISPID_TITLECHANGE, WebBrowserEvents2_TitleChange, NULL)
	SINK_ENTRY_INFO(1, __uuidof(DWebBrowserEvents2), DISPID_NAVIGATECOMPLETE2, WebBrowserEvents2_NavigateComplete2, NULL)
	SINK_ENTRY_INFO(1, __uuidof(DWebBrowserEvents2), DISPID_NEWWINDOW2, WebBrowserEvents2_NewWindow2, NULL)
	SINK_ENTRY_INFO(1, __uuidof(DWebBrowserEvents2), DISPID_NEWWINDOW3, WebBrowserEvents2_NewWindow3, NULL)
	SINK_ENTRY_INFO(1, __uuidof(DWebBrowserEvents2), DISPID_ONQUIT, WebBrowserEvents2_OnQuit, NULL)
	SINK_ENTRY_INFO(1, __uuidof(DWebBrowserEvents2), DISPID_WINDOWCLOSING, WebBrowserEvents2_WindowClosing, NULL)
END_SINK_MAP()

	STDMETHOD(WebBrowserEvents2_BeforeNavigate2)(IDispatch* pDisp, VARIANT *url, VARIANT *varFlags, VARIANT *varTargetFrameName, 
												 VARIANT *varPostData, VARIANT *varHeaders, VARIANT_BOOL *varCancel);
	STDMETHOD(WebBrowserEvents2_CommandStateChange)(long nCommand, VARIANT_BOOL vbEnable);
	STDMETHOD(WebBrowserEvents2_DocumentComplete)(IDispatch* pDisp, VARIANT* URL);
	STDMETHOD(WebBrowserEvents2_NavigateComplete2)(IDispatch *pDisp, VARIANT* URL);
	STDMETHOD(WebBrowserEvents2_NewWindow2)(IDispatch *pDisp, VARIANT_BOOL bCancel);
	STDMETHOD(WebBrowserEvents2_NewWindow3)(IDispatch **&pdispInterface, VARIANT_BOOL vbCancel, DWORD dwFlags, BSTR bstrUrlContext, BSTR bstrUrl);
	STDMETHOD(WebBrowserEvents2_OnQuit)();
	STDMETHOD(WebBrowserEvents2_ProgressChange)(long nProgressChange, long nProgressChangeMax);
	STDMETHOD(WebBrowserEvents2_StatusTextChange)(BSTR bstrText);
	STDMETHOD(WebBrowserEvents2_TitleChange)(BSTR bstrText);
	STDMETHOD(WebBrowserEvents2_WindowClosing)(VARIANT_BOOL vbIsChildWindow, VARIANT_BOOL &vbCancel);

// Override the GetFuncInfoFromId function
// this is the only way I could get the IDispEventSimpleImpl interface to work
// http://support.microsoft.com/kb/194179/EN-US
	HRESULT GetFuncInfoFromId(const IID& iid, DISPID dispidMember, LCID lcid, _ATL_FUNC_INFO& info)
	{
		if (InlineIsEqualGUID(iid, __uuidof(DWebBrowserEvents2)))
		{
		   info.cc = CC_STDCALL;
		   switch(dispidMember)
		   {
			   case DISPID_COMMANDSTATECHANGE:
				   info.vtReturn = VT_EMPTY;
				   info.nParams = 2;
				   info.pVarTypes[0] = VT_I4;
				   info.pVarTypes[1] = VT_BOOL;
				   return S_OK;

			  case DISPID_DOCUMENTCOMPLETE:
				 info.vtReturn = VT_EMPTY;
				 info.nParams = 2;
				 info.pVarTypes[0] =  VT_DISPATCH;
				 info.pVarTypes[1] = VT_VARIANT | VT_BYREF;
				 return S_OK;
				 
			  case DISPID_BEFORENAVIGATE2:
				  info.vtReturn = VT_EMPTY;
				  info.nParams = 7;
				  info.pVarTypes[0] = VT_DISPATCH;
				  info.pVarTypes[1] = VT_VARIANT | VT_BYREF;
				  info.pVarTypes[2] = VT_VARIANT | VT_BYREF;
				  info.pVarTypes[3] = VT_VARIANT | VT_BYREF;
				  info.pVarTypes[4] = VT_VARIANT | VT_BYREF;
				  info.pVarTypes[5] = VT_VARIANT | VT_BYREF;
				  info.pVarTypes[6] = VT_BOOL | VT_BYREF;
				  return S_OK;

			  case DISPID_PROGRESSCHANGE:
				  info.vtReturn = VT_EMPTY;
				  info.nParams = 2;
				  info.pVarTypes[0] = VT_I4;
				  info.pVarTypes[1] = VT_I4;
				  return S_OK;

			  case DISPID_STATUSTEXTCHANGE:
				  info.vtReturn = VT_EMPTY;
				  info.nParams = 1;
				  info.pVarTypes[0] = VT_BSTR;
				  return S_OK;
				  
			  case DISPID_TITLECHANGE:
				  info.vtReturn = VT_EMPTY;
				  info.nParams = 1;
				  info.pVarTypes[0] = VT_BSTR;
				  return S_OK;

			  case DISPID_NAVIGATECOMPLETE2:
				  info.vtReturn = VT_EMPTY;
				  info.nParams = 2;
				  info.pVarTypes[0] = VT_DISPATCH;
				  info.pVarTypes[1] = VT_VARIANT | VT_BYREF;
				  return S_OK;
				  
			  case DISPID_NEWWINDOW2:
				  info.vtReturn = VT_EMPTY;
				  info.nParams = 2;
				  info.pVarTypes[0] = VT_DISPATCH;
				  info.pVarTypes[1] = VT_BOOL | VT_BYREF;
				  return S_OK;

			  case DISPID_NEWWINDOW3:
				  info.vtReturn = VT_EMPTY;
				  info.nParams = 5;
				  info.pVarTypes[0] = VT_DISPATCH | VT_BYREF;
				  info.pVarTypes[1] = VT_BOOL | VT_BYREF;
				  info.pVarTypes[2] = VT_I4;
				  info.pVarTypes[3] = VT_BSTR;
				  info.pVarTypes[4] = VT_BSTR;
				  return S_OK;

			  case DISPID_ONQUIT:
				  info.vtReturn = VT_EMPTY;
				  info.nParams = 0;
				  return S_OK;

			  case DISPID_WINDOWCLOSING:
				  info.vtReturn = VT_EMPTY;
				  info.nParams = 2;
				  info.pVarTypes[0] = VT_BOOL;
				  info.pVarTypes[1] = VT_BOOL | VT_BYREF;
				  return S_OK;


			  default:
				 return E_FAIL;
		   }
		}
		return E_FAIL;
	}

	// override IOleInPlaceActiveObjectImpl<CIEBrowser>::TranslateAccelerator
	// so we can capture TABs, arrow keys, and other key strokes in the m_spIEBrowser
	// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/com/html/f755b919-b810-4b66-b3c2-bf38bd525d60.asp
	STDMETHOD(TranslateAccelerator)(MSG *pMsg);


protected:
	void ProcessDocument(MSHTML::IHTMLDocument2* pDoc);
	void ProcessElementCollection(MSHTML::IHTMLElementCollection* pElemColl);
	void ConnectEvents(MSHTML::IHTMLElement2 *pElem);
	//
	void ReleaseDocument(MSHTML::IHTMLDocument2 *phtmlDocument);
	void ReleaseElementCollection(MSHTML::IHTMLElementCollection* pElemColl);
	void DisconnectEvents(MSHTML::IHTMLElement *pElem);
	//
	STDMETHOD(Invoke)(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pdispparams,
                                VARIANT* pvarResult, EXCEPINFO* pexcepinfo, UINT* puArgErr);
	void HTMLElementEvents2_OnMouseDown(IDispatch *pDisp);
	void HTMLElementEvents2_OnMouseUp(IDispatch *pDisp);
	void HTMLElementEvents_OnClick(IDispatch *pDisp);
	void HTMLElementEvents_OnMouseOver(IDispatch *pDisp);
	void HTMLElementEvents_OnChange(IDispatch *pDisp);
	void HTMLInputTextElementEvents_OnChange(IDispatch *pDisp);
	void HTMLInputTextElementEvents_OnKeyDown(IDispatch *pDisp);
	void HTMLInputTextElementEvents_OnKeyPress(IDispatch *pDisp);
	void HTMLFormElementEvents_OnSubmit(IDispatch *pDisp);
	void HTMLSelectElementEvents_OnChange(IDispatch *pDisp);
	void HTMLFileElementEvents_OnChange(IDispatch *pDisp);
	void HTMLDocumentEvents_OnSelect(IDispatch *pDisp);

	// helper methods
	_bstr_t GetActiveSelection(MSHTML::IHTMLDocument2 *piDoc2, 
							   MSHTML::IHTMLElement **piSelectionElement);
	long GetHTMLInterfaces(IDispatch *pdispEvent, MSHTML::IHTMLEventObj **peventEventObj, MSHTML::IHTMLElement **pelemHTML);
	
	HRESULT GetHTMLInterfacesFromIDispatch(IDispatch *pDisp, MSHTML::IHTMLEventObj **phtmlEventObj, MSHTML::IHTMLElement **phtmlElement);
	HRESULT GetTagAndType(MSHTML::IHTMLElement *pElem, _bstr_t *_bstrTag, _bstr_t *_bstrType);
	void ReleaseDOM();
	void ProcessDOM();
	long IsolateEvent(long nDispPtr, MSHTML::IHTMLElement *pelemSrc, MSHTML::IHTMLEventObj *peventEvent);
	BOOL CheckDocumentComplete(MSHTML::IHTMLDocument2 *pdocRoot);
	

private:

	// GDI+ stuff
	Gdiplus::GdiplusStartupInput m_oGdiPlusStartupInput;
	ULONG_PTR m_nGdiPlusToken;
	int GetEncoderClsid(const WCHAR *format, CLSID *pClsid);
	

	MAPADVISE m_mapAdviseCookies;
	VECTORIUNKNOWNPOINTER vectorDocuments;
	HANDLE m_hDocumentComplete;
	HWND m_hwndApp;
	HWND m_hwndParent;
	HWND m_hwndMain;        
	BOOL bDestroy;
	_bstr_t m_bstrBrowserID;

	IDispatch *m_pdispMaster;
	long m_nMasterProcess;

	// onclick variables
	long m_nOnClickID;
	BOOL m_bIgnoreEvents;
	

	long m_nDispID1;
	long m_nDispID2;
	long m_nDispPtr;
	long m_bDispContinue;
	long m_nIsolateSourceIndex;

	BOOL m_MousedownIsMouseclick;
};


#endif //__IEBROWSER_H_
